/**
 * @fileOverview entry file
 * @author huangtonger@aliyun.com
 */

const Util = require('../util');
const Command = {};

// 判断是否有子项被选中
function hasItemSelected(editor) {
  const page = editor.getCurrentPage();
  const items = page.getSelected();
  return items.length > 0;
}

// 执行命令前存储快照
function snapShotexecute(editor) {
  const page = editor.getCurrentPage();
  this.snapShot = page.save();
  this.selectedItems = page.getSelected().map(item => {
    return item.id;
  });
  if (this.method) {
    if (Util.isString(this.method)) {
      page[this.method]();
    } else {
      this.method(editor);
    }
  }
}

// 通过快照回滚
function snapShotBack(editor) {
  const page = editor.getCurrentPage();
  page.read(this.snapShot);
  page.setSelected(this.selectedItems, true);
}

// 变更模型判断是否可用
function changeModeEnable(editor) {
  const page = editor.getCurrentPage();
  return page.getMode() !== this.toMode;
}

// 切换模式类执行方法
function changeModeexecute(editor) {
  const page = editor.getCurrentPage();
  this.fromMode = page.getMode();
  page.changeMode(this.toMode);
}

// 模式回滚
function changeModeBack(editor) {
  const page = editor.getCurrentPage();
  page.changeMode(this.fromMode);
}

Command.list = [];

Command.registerCommand = (name, cfg, extend) => {
  if (Command[name]) {
    Util.mix(Command[name], cfg);
  } else {
    let command = {
      enable() {
        return true;
      },
      init() {
      },
      execute: snapShotexecute,
      back: snapShotBack,
      shortcutCodes: undefined,
      executeTimes: 1,
      name,
      queue: true,
      ...cfg
    };
    if (extend && Command[extend]) {
      command = Util.mix({}, Command[extend], cfg);
    }
    Command[name] = command;
    Command.list.push(command);
  }
};

Command.execute = (name, editor, obj) => {
  const command = Util.mix(true, {}, Command[name], obj);
  const cfg = editor.get('_command');
  if (command.enable(editor)) {
    command.init();
    if (command.queue) {
      cfg.queue.splice(
        cfg.current,
        cfg.queue.length - cfg.current,
        command);
      cfg.current += 1;
    }
    editor.emit('beforecommandexecute', {
      command
    });
    command.execute(editor);
    editor.emit('aftercommandexecute', {
      command
    });
    editor.setCommandDOMenable(); // 刷新按钮可用情况
  }
  return command;
};

Command.enable = (name, editor) => {
  return Command[name].enable(editor);
};

Command.registerCommand('common', {
  back: snapShotBack
});

Command.registerCommand('copyAdjacent', {
  enable() {
    return this.copyNode && this.copyNode.isNode && this.x && this.y;
  },
  execute(editor) {
    const {copyNode} = this;
    const page = editor.getCurrentPage();
    const graph = page.getGraph();
    const copyNodeModel = copyNode.getModel();
    const model = Util.clone(copyNodeModel);
    model.id = Util.guid();
    model.x = this.x;
    model.y = this.y;
    const addNode = graph.add('node', model);
    const addEdge = graph.add('edge', {
      source: copyNodeModel.id,
      target: model.id
    });
    if (this.executeTimes === 1) {
      this.addIds = [addNode.id, addEdge.id];
      this.page = page;
    }
  },
  back() {
    const page = this.page;
    const graph = page.getGraph();
    this.addIds.forEach(addId => {
      graph.remove(addId);
    });
  }
});

// 添加命令
Command.registerCommand('add', {
  enable() {
    return this.type && this.addModel;
  },
  execute(editor) {
    const page = editor.getCurrentPage();
    const graph = page.getGraph();
    const addItem = graph.add(this.type, this.addModel);
    if (this.executeTimes === 1) {
      this.addId = addItem.id;
      this.page = page;
    }
  },
  back() {
    const page = this.page;
    const graph = page.getGraph();
    graph.remove(this.addId);
  }
});

// 更新命令
Command.registerCommand('update', {
  enable() {
    return this.itemId && this.updateModel;
  },
  execute(editor) {
    const page = editor.getCurrentPage();
    const graph = page.getGraph();
    const item = graph.find(this.itemId);
    if (this.executeTimes === 1) {
      this.originModel = Util.getContrast(item.getModel(), this.updateModel);
      this.page = page;
    }
    graph.update(item, this.updateModel);
  },
  back() {
    const page = this.page;
    const graph = page.getGraph();
    const item = graph.find(this.itemId);
    graph.update(item, this.originModel);
  }
});

// 重做
Command.registerCommand('redo', {
  queue: false,
  enable(editor) {
    const cfg = editor.get('_command');
    const queue = cfg.queue;
    return cfg.current < queue.length;
  },
  execute(editor) {
    const cfg = editor.get('_command');
    const queue = cfg.queue;
    const current = cfg.current;
    queue[current].execute(editor);
    cfg.current += 1;
  },
  shortcutCodes: [
    ['metaKey', 'shiftKey', 'z'],
    ['ctrlKey', 'shiftKey', 'z']
  ]
});

// 撤销
Command.registerCommand('undo', {
  queue: false,
  enable(editor) {
    const cfg = editor.get('_command');
    return cfg.current > 0;
  },
  execute(editor) {
    const cfg = editor.get('_command');
    const queue = cfg.queue;
    const current = cfg.current;
    const command = queue[current - 1];
    command.executeTimes++;
    command.back(editor);
    cfg.current -= 1;
  },
  shortcutCodes: [
    ['metaKey', 'z'],
    ['ctrlKey', 'z']
  ]
});


function getPaste(clipboard) {
  const oldnewmap = {};
  const rst = [];
  clipboard.forEach(data => {
    const model = data.model;
    const newModel = Util.mix(true, {}, {
      ...model,
      id: Util.guid()
    });
    oldnewmap[model.id] = newModel.id;
    rst.push({
      ...data,
      model: newModel
    });
  });
  rst.forEach(data => {
    const model = data.model;
    if (model.parent) {
      const newParentId = oldnewmap[model.parent];
      if (newParentId) {
        model.parent = newParentId;
      } else {
        delete model.parent;
      }
    }
  });
  rst.sort((a, b) => {
    return a.index - b.index;
  });
  return rst;
}

Command.registerCommand('copy', {
  queue: false,
  enable: hasItemSelected,
  method(editor) {
    const page = editor.getCurrentPage();
    const selectedItems = page.getSelected();
    const cfg = editor.get('_command');
    const selectedGroups = selectedItems.map(item => {
      return item.getGraphicGroup();
    });
    const box = Util.getChildrenBBox(selectedGroups);
    cfg.clipboard = [];
    selectedItems.forEach(item => {
      Util.traverseTree(item, child => {
        const model = child.getModel();
        const group = child.getGraphicGroup();
        cfg.clipboard.push({
          type: child.type,
          index: Util.getIndex(group),
          model
        });
      }, parent => {
        return parent.getChildren && parent.getChildren();
      }, true);
    });
    editor.set('copyCenterBox', {
      ...box
    });
  }
});

Command.registerCommand('pasteHere', {
  enable(editor) {
    const cfg = editor.get('_command');
    return cfg.clipboard.length > 0;
  },
  method(editor) {
    const page = editor.getCurrentPage();
    const cfg = editor.get('_command');
    const pasteData = this.pasteData ? this.pasteData : getPaste(cfg.clipboard);
    const copyCenterBox = this.copyCenterBox ? this.copyCenterBox : editor.get('copyCenterBox');
    const contextmenuPoint = this.contextmenuPoint ? this.contextmenuPoint : editor.get('contextmenuPoint');
    page.clearSelected();
    this.copyCenterBox = Util.clone(copyCenterBox);
    this.pasteData = Util.clone(pasteData);
    this.contextmenuPoint = Util.clone(contextmenuPoint);
    pasteData.forEach(data => {
      const model = data.model;
      if (model.x) {
        model.x += (contextmenuPoint.x - copyCenterBox.minX);
      }
      if (model.y) {
        model.y += (contextmenuPoint.y - copyCenterBox.minY);
      }
      page.add(data.type, model);
    });
  },
  back: snapShotBack
});

Command.registerCommand('paste', {
  enable(editor) {
    const cfg = editor.get('_command');
    return cfg.clipboard.length > 0;
  },
  method(editor) {
    const page = editor.getCurrentPage();
    const cfg = editor.get('_command');
    const pasteData = this.pasteData ? this.pasteData : getPaste(cfg.clipboard);
    page.clearSelected();
    this.pasteData = Util.clone(pasteData);
    pasteData.forEach(data => {
      const model = data.model;
      if (model.x) {
        model.x += 10;
      }
      if (model.y) {
        model.y += 10;
      }
      page.add(data.type, model);
    });
  },
  back: snapShotBack
});


Command.registerCommand('addGroup', {
  init() {
    this.addGroupId = Util.guid();
  },
  enable(editor) {
    const page = editor.getCurrentPage();
    const items = page.getSelected();
    return items.length > 1;
  },
  method(editor) {
    const page = editor.getCurrentPage();
    page.addGroup({
      id: this.addGroupId
    });
  },
  back: snapShotBack
});

Command.registerCommand('unGroup', {
  enable(editor) {
    const page = editor.getCurrentPage();
    const selectedItems = page.getSelected();
    return selectedItems.length === 1 && selectedItems[0].isGroup;
  },
  method: 'unGroup',
  back: snapShotBack
});

Command.registerCommand('delete', {
  getDeleteItems(editor) {
    const page = editor.getCurrentPage();
    const graph = page.getGraph();
    let items = this.itemIds ? this.itemIds.map(itemId => {
      return graph.find(itemId);
    }) : page.getSelected();
    items = items.filter(item => {
      return item.deleteable !== false;
    });
    return items;
  },
  enable(editor) {
    const items = this.getDeleteItems(editor);
    return items.length > 0;
  },
  method(editor) {
    const page = editor.getCurrentPage();
    const items = this.getDeleteItems(editor);
    const graph = page.getGraph();

    page.emit('beforedelete', {
      items
    });
    Util.each(items, item => {
      graph.remove(item);
    });
    page.emit('afterdelete', {
      items
    });
    this.itemIds = items.map(item => {
      return item.getModel().id;
    });
  },
  back: snapShotBack,
  shortcutCodes: ['Delete', 'Backspace']
});

Command.registerCommand('selectAll', {
  method(editor) {
    const page = editor.getCurrentPage();
    const graph = page.getGraph();
    const items = graph.getItems();
    items.forEach(item => {
      page.setSelected(item, true);
    });
  },
  back: snapShotBack,
  shortcutCodes: [
    ['metaKey', 'a']
  ]
});

Command.registerCommand('toBack', {
  enable: hasItemSelected,
  method: 'toBack',
  back: snapShotBack
});

Command.registerCommand('toFront', {
  enable: hasItemSelected,
  method: 'toFront',
  back: snapShotBack
});

Command.registerCommand('clear', {
  enable(editor) {
    const page = editor.getCurrentPage();
    const items = page.getItems();
    return items.length > 0;
  },
  method: 'clear',
  back: snapShotBack
});

Command.registerCommand('multiSelect', {
  enable: changeModeEnable,
  toMode: 'multiSelect',
  execute: changeModeexecute,
  back: changeModeBack
});

Command.registerCommand('move', {
  enable: changeModeEnable,
  toMode: 'move',
  execute: changeModeexecute,
  back: changeModeBack
});

Command.registerCommand('verticalAlign', {
  enable(editor) {
    const page = editor.getCurrentPage();
    const items = page.getSelected();
    return items.length > 1;
  },
  method(editor) {
    const page = editor.getCurrentPage();
    const selectedItems = page.getSelected();
    const graph = page.getGraph();
    const firstItemY = selectedItems[0].model.y;

    selectedItems.forEach(item => {
      const model = item.model;
      model.y=firstItemY;
      graph.update(item,model);
    })
  },
  back: snapShotBack
});

Command.registerCommand('horizontalAlign', {
  enable(editor) {
    const page = editor.getCurrentPage();
    const items = page.getSelected();
    return items.length > 1;
  },
  method(editor) {
    const page = editor.getCurrentPage();
    const selectedItems = page.getSelected();
    const graph = page.getGraph();
    const firstItemX = selectedItems[0].model.x;
    selectedItems.forEach(item => {
      const model = item.model;
      model.x=firstItemX;
      graph.update(item,model);
    })
  },
  back: snapShotBack
});
module.exports = Command;
